In this version of MyRent, we introduce a range of UI Widgets to evolve the UX into something more useful. These widgets will be 'active', meaning that the host Activity will be intercepting and responding to events the user may generate when interacting with the application.
At the end of this topic our goal is to have a screen looking something like this:
We will have added:
In the previous step we introduced a listener for the geolocation input (the latitude-longitude string).
In this step we shall:
Continue building the MyRent app that you commenced in the previous lab.
Before we start to expand the project, we need to perform some rearranging so it can be extended in an orderly manner.
This is our current application workspace:
.. and this is a version we used in writing this lab:
Here is a detailed description on how this may be achieved:
Before proceeding make a name use the menu refactor command to rename MyRentActivity to ResidenceAcivity.
The previous iteration has one UI control, an EditText.
This iteration shall introduce:
Button
Select activity_myrent.xml, right click and using context menu commands Refactor | Rename change the name to activity_residence.xml.
Open activity_residence.xml.
Remove background, text and hint colouring (if not already done). This was introduced simply to demonstrate how it might be achieved. The refactored file is as follows:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="app.myrent.ResidenceActivity" >
<EditText
android:id="@+id/geolocation"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentRight="true"
android:hint="@string/geolocation_hint"/>
</RelativeLayout>
A number of different approaches are available to change the layout.
In graphical display mode drag a Button onto the MyRent canvas and resize using the handles so as to extend full width of the screen as shown in Figure 1.
Double click Button in Design view and complete the text (blank) and id (registration_date) fields as depicted in Figure 2.
Here is the resulting strings.xml file:
<resources>
<string name="app_name">MyRent</string>
<string name="title_activity_myrent">ResidenceActivity</string>
<string name="geolocation_hint">52.253456,-7.187162</string>
<string name="action_settings">Settings</string>
<string name="registration_date"/>
</resources>
Open activity_residence.xml in Text mode and add this code to the Button element to set the margins:
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
Observe in the Design view how button resizes (Figure 3).
Here is the refactored activity_residence.xml. Notice that we are changing the layout type to LinearLayout.
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<EditText
android:id="@+id/geolocation"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/geolocation_hint">
</EditText>
<Button
android:id="@+id/registration_date"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"/>
</LinearLayout>
Observe that a button id has now been generated in the R.java file:
Next we shall add a section label and divider immediately before the geolocation node.
<string name="location">Location</string>
Here is the completed xml node for the location label:
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/location"
style="?android:listSeparatorTextViewStyle"/>
Implement these modifications and inspect the result in the Graphical Layout. You should be presented with that shown in Figure 5:
Finally, in this step, add a section label for status.
Here is the xml:
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/status"
style="?android:listSeparatorTextViewStyle"
/>
Add the referenced string resource status in res/values/strings.xml:
<string name="status">Status</string>
The result is shown in Figure 6.
We shall continue with the development of the layout in the following steps.
Here is the refactored activity_residence layout:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
style="?android:listSeparatorTextViewStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/location"/>
<EditText
android:id="@+id/geolocation"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/geolocation_hint">
</EditText>
<TextView
style="?android:listSeparatorTextViewStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/status"/>
<Button
android:id="@+id/registration_date"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"/>
</LinearLayout>
We shall now complete remaining work on the layout using the Graphical Layout and the Outline panel.
Here is the the layout at this stage of development.
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<!-- LOCATION -->
<TextView
style="?android:listSeparatorTextViewStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/location" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:baselineAligned="false"
android:orientation="horizontal" >
<!-- Geolocation (GPS Coords) -->
<EditText
android:id="@+id/geolocation"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/geolocation_hint" >
<requestFocus />
</EditText>
</LinearLayout>
<!-- STATUS -->
<TextView
style="?android:listSeparatorTextViewStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/status" />
<Button android:id="@+id/registration_date"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
/>
</LinearLayout>
It remains only to add the checkbox.
With the Graphical Layout open, drag a CheckBox from the Form Widgets folder and drop directly underneath the registration_date button in the Outline panel.
Replace android:text attribute with string referenced in strings.xml and change the android:id, all as shown here:
<CheckBox
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/isrented"
android:id="@+id/isrented"
android:checked="false"/>
Add a string resource for the checkbox:
<string name="isrented">Rented?</string>
Because we have moved MyRentActivity to a new folder, a change to the manifest file is required:
<activity android:name=".activities.MyRentActivity">
This concludes the layout design and implementation in this step.
The hierarchical arrangement of the layout is shown here in Figure 3.
Replace your Residence class with the following:
package org.wit.myrent.models;
import java.util.Date;
import java.util.Random;
public class Residence
{
public Long id;
public Long date;
//a latitude longitude pair
//example "52.4566,-6.5444"
private String geolocation;
public boolean rented;
public Residence()
{
id = unsignedLong();
date = new Date().getTime();
}
/**
* Generate a long greater than zero
* @return Unsigned Long value greater than zero
*/
private Long unsignedLong() {
long rndVal = 0;
do {
rndVal = new Random().nextLong();
} while (rndVal <= 0);
return rndVal;
}
public void setGeolocation(String geolocation)
{
this.geolocation = geolocation;
}
public String getGeolocation()
{
return geolocation;
}
public String getDateString() {
return "Registered:" + dateString();
}
private String dateString() {
String dateFormat = "EEE d MMM yyyy H:mm";
return android.text.format.DateFormat.format(dateFormat, date).toString();
}
}
Note that we have made the fields public for convenience. Also, we have introduced a new date and rented fields into the model.
Change the name of MyRentActivity to ResidenceActivity. This naming convention will prove more intuitive and consistent as you will see in later development iterations.
In ResidenceActivity introduce 2 new fields to access the new widgets we have just introduced:
private CheckBox rented;
private Button dateButton;
and on OnCreate, we need to initialize these:
dateButton = (Button) findViewById(R.id.registration_date);
rented = (CheckBox) findViewById(R.id.isrented);
rented.setOnCheckedChangeListener(this);
Furthmore, we are going to disable the date button when the activity is created:
dateButton .setEnabled(false);
Run the app now, and verify that the activity launches without incident.
We would now like to engage the checkbox rented. First, implement the OnCheckedChangeListener interface:
public class ResidenceActivity extends Activity implements TextWatcher, OnCheckedChangeListener
{
This will require the following import:
import android.widget.CompoundButton.OnCheckedChangeListener;
and this is the implementation:
@Override
public void onCheckedChanged(CompoundButton arg0, boolean isChecked)
{
Log.i(this.getClass().getSimpleName(), "rented Checked");
residence.rented = isChecked;
}
This completes the class. Here is the complete code to this stage:
package org.wit.myrent.activities;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.EditText;
import org.wit.myrent.R;
import org.wit.myrent.models.Residence;
public class ResidenceActivity extends AppCompatActivity implements TextWatcher, CompoundButton.OnCheckedChangeListener
{
private EditText geolocation;
private Residence residence;
private CheckBox rented;
private Button dateButton;
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_myrent);
geolocation = (EditText) findViewById(R.id.geolocation);
residence = new Residence();
// Register a TextWatcher in the EditText geolocation object
geolocation.addTextChangedListener(this);
dateButton = (Button) findViewById(R.id.registration_date);
rented = (CheckBox) findViewById(R.id.isrented);
dateButton .setEnabled(false);
}
@Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2)
{
}
@Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2)
{
}
@Override
public void afterTextChanged(Editable editable)
{
residence.setGeolocation(editable.toString());
}
@Override
public void onCheckedChanged(CompoundButton compoundButton, boolean isChecked)
{
Log.i(this.getClass().getSimpleName(), "rented Checked");
residence.rented = isChecked;
}
}
Run the app and check that the layout presented is as expected, something like that shown in Figure 1.
Run the app and use the debugger to ensure data is being transmitted to and from the Residence object.
Examine the state of the variables such as:
Press the Run to Cursor toolbar icon to run to completion.
Here is what we have achieved in this topic:
Added widgets to the layout
Added a listener in the controller to detected changes in the UI checkbox state and transmit any state changes to the model Residence object
Added a date field to the model and intialized this at the time a residence object created which represents the registration date of the property with the MyRent app.
Described how to conduct a simple test using the debugger to verify that the listeners operate correctly and that UI data transmission takes place successfully in both directions between model and UI.
The application at the end of this lab is available for reference here:
https://github.com/wit-ictskills-2016/myrent-01.git